package video

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/samber/lo"
	"github.com/spf13/cast"
	"gorm.io/gorm"
	"log/slog"
	"net/textproto"
	"strconv"
	"strings"
	"videoPlay/internal/dal/dalModels"
	"videoPlay/internal/http/handlers/video/httpModels"
	"videoPlay/internal/http/response"
	"videoPlay/internal/services/durationTag"
	"videoPlay/internal/services/video"
	"videoPlay/internal/services/videoTag"
)

type Handler struct {
	deleteVideoPassword string
	serviceVideo        *video.Service
	serviceTag          *videoTag.Service
	serviceStarDuration *durationTag.Service
}

func New(deleteVideoPassword string, sVideo *video.Service, sTag *videoTag.Service, sStarDuration *durationTag.Service) *Handler {
	return &Handler{
		deleteVideoPassword: deleteVideoPassword,
		serviceVideo:        sVideo,
		serviceTag:          sTag,
		serviceStarDuration: sStarDuration,
	}
}

func (a *Handler) Router(r gin.IRouter) {
	r.GET("video/list", a.getVideoList)
	r.GET("video/get/random", a.getVideoByRandom)
	r.GET("video/:id/preview", a.preview)
	r.GET("video/:id/previewVideo", a.previewVideo)
	r.GET("video/:id/media", a.media)
	r.POST("video/:id/videoTag/add", a.addVideoTagForVideo)
	r.POST("video/:id/durationTag/add", a.addDurationTagForVideo)
	r.DELETE("video/:id", a.deleteVideo)

	r.DELETE("videoTag/:id", a.deleteVideoTag)
	r.DELETE("durationTag/:id", a.deleteDurationTag)
}

func (a *Handler) getVideoList(c *gin.Context) {
	input := new(httpModels.ReqGetVideoList)
	if err := c.ShouldBindQuery(input); err != nil {
		response.BadRequestWithMsg(c, err.Error())
		return
	}
	if input.Size == 0 {
		input.Size = 16
	}
	videoList, err := a.serviceVideo.GetVideoList(input.Keywords, &input.Pager)
	if err != nil {
		slog.Error("获取视频列表失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	ret := &httpModels.RespGetVideoList{
		Pager: input.Pager,
		Data: lo.Map(videoList, func(v *dalModels.Video, _ int) *httpModels.Video {
			return new(httpModels.Video).FromDalModel(v)
		}),
	}
	response.SuccessWithData(c, ret)
}

func (a *Handler) getVideoByRandom(c *gin.Context) {
	input := new(httpModels.ReqGetVideoByRandom)
	if err := c.ShouldBindQuery(input); err != nil {
		response.BadRequestWithMsg(c, err.Error())
		return
	}
	v, err := a.serviceVideo.GetVideoByRandom(input.Keywords)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			response.BadRequestWithMsg(c, "没有更多视频了")
			return
		}
		slog.Error("获取视频失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	response.SuccessWithData(c, new(httpModels.Video).FromDalModel(v))
}

func (a *Handler) deleteVideo(c *gin.Context) {
	videoId := uuid.MustParse(c.Param("id"))
	input := new(httpModels.ReqDeleteVideo)
	if err := c.ShouldBindJSON(input); err != nil {
		response.BadRequestWithMsg(c, err.Error())
		return
	}
	if input.Password != a.deleteVideoPassword {
		response.ForbiddenWithMsg(c, "密码不正确")
		return
	}
	err := a.serviceVideo.DeleteVideoById(videoId, false)
	if err != nil {
		slog.Error("删除视频记录失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	response.SuccessWithData(c, httpModels.RespDeleteVideo{VideoId: videoId})
}

func (a *Handler) preview(c *gin.Context) {
	videoId := c.Param("id")
	v, err := a.serviceVideo.GetVideoById(uuid.MustParse(videoId))
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			response.BadRequestWithMsg(c, "videoId不存在")
			return
		}
		slog.Error("获取视频记录失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	c.Writer.Header().Set("Content-Type", "image/png")
	c.File(v.PreviewImgSrc)
}

func (a *Handler) previewVideo(c *gin.Context) {
	videoId := c.Param("id")
	v, err := a.serviceVideo.GetVideoById(uuid.MustParse(videoId))
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			response.BadRequestWithMsg(c, "videoId不存在")
			return
		}
		slog.Error("获取视频记录失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	c.File(v.PreviewVideoSrc)
}

func (a *Handler) media(c *gin.Context) {
	videoId := c.Param("id")
	v, err := a.serviceVideo.GetVideoById(uuid.MustParse(videoId))
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			response.BadRequestWithMsg(c, "videoId不存在")
			return
		}
		slog.Error("获取视频记录失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	ranges, err := parseRange(c.Request.Header.Get("range"))
	if err != nil {
		response.BadRequestWithMsg(c, err.Error())
		return
	}
	if ranges != nil { // 当有使用range请求时, 检查是否有截止位置, 没有则默认10M
		const (
			kb = 1024
			mb = 1024 * kb
		)
		for i := range ranges {
			if ranges[i].end == 0 {
				ranges[i].end = ranges[i].start + 10*mb
			}
		}
		reduce := lo.Reduce(ranges, func(agg string, item httpRange, index int) string {
			if index > 0 {
				return fmt.Sprintf("%s%d-%d,", agg, item.start, item.end)
			}
			return fmt.Sprintf("%s%d-%d", agg, item.start, item.end)
		}, "bytes=")
		c.Request.Header.Set("Range", reduce)
	}
	c.File(v.Src)
}

type httpRange struct {
	start, end int64
}

var errNoOverlap = errors.New("invalid range: failed to overlap")

func parseRange(s string) ([]httpRange, error) {
	if s == "" {
		return nil, nil // header not present
	}
	const b = "bytes="
	if !strings.HasPrefix(s, b) {
		return nil, errors.New("invalid range")
	}
	var ranges []httpRange
	for _, ra := range strings.Split(s[len(b):], ",") {
		ra = textproto.TrimString(ra)
		if ra == "" {
			continue
		}
		start, end, ok := strings.Cut(ra, "-")
		if !ok {
			return nil, errors.New("invalid range")
		}
		start, end = textproto.TrimString(start), textproto.TrimString(end)
		var r httpRange
		var err error
		if start == "" || end == "" {
			if start == "" && end == "" {
				return nil, errors.New("invalid range")
			}
			if start == "" {
				r.end, err = strconv.ParseInt(end, 10, 64)
				if err != nil {
					return nil, err
				}
			} else {
				r.start, err = strconv.ParseInt(start, 10, 64)
				if err != nil {
					return nil, err
				}
			}
		} else {
			r.start, err = strconv.ParseInt(start, 10, 64)
			if err != nil {
				return nil, err
			}
			r.end, err = strconv.ParseInt(end, 10, 64)
			if err != nil {
				return nil, err
			}
		}
		ranges = append(ranges, r)
	}
	if len(ranges) == 0 {
		// The specified ranges did not overlap with the content.
		return nil, errNoOverlap
	}
	return ranges, nil
}

func (a *Handler) addVideoTagForVideo(c *gin.Context) {
	videoId := uuid.MustParse(c.Param("id"))
	input := new(httpModels.ReqAddVideoTagForVideo)
	if err := c.ShouldBindJSON(input); err != nil {
		response.BadRequestWithMsg(c, err.Error())
		return
	}
	v, err := a.serviceVideo.GetVideoById(videoId)
	if err != nil {
		slog.Error("获取视频记录失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	// 创建标签
	err = a.serviceVideo.AddVideoTag(v, input.Name)
	if err != nil {
		slog.Error("视频标签添加失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	response.SuccessWithData(c, new(httpModels.VideoTag).FromDalModel(v.VideoTags[len(v.VideoTags)-1]))
}

func (a *Handler) deleteVideoTag(c *gin.Context) {
	tagId := cast.ToUint(c.Param("id"))
	err := a.serviceTag.DeleteById(tagId)
	if err != nil {
		slog.Error("视频标签删除失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	response.SuccessWithData(c, httpModels.RespDeleteVideoTag{Id: tagId})
}

func (a *Handler) addDurationTagForVideo(c *gin.Context) {
	videoId := uuid.MustParse(c.Param("id"))
	input := new(httpModels.ReqAddDurationTagForVideo)
	if err := c.ShouldBindJSON(input); err != nil {
		response.BadRequestWithMsg(c, err.Error())
		return
	}
	v, err := a.serviceVideo.GetVideoById(videoId)
	if err != nil {
		slog.Error("获取视频记录失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	// 创建时间标签
	err = a.serviceVideo.AddDurationTag(v, input.StarSecond, input.Content)
	if err != nil {
		slog.Error("时间标签添加失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	response.SuccessWithData(c, new(httpModels.DurationTag).FromDalModel(v.DurationTags[len(v.DurationTags)-1]))
}

func (a *Handler) deleteDurationTag(c *gin.Context) {
	starDurationId := cast.ToUint(c.Param("id"))
	// 删除收藏的时间点
	err := a.serviceStarDuration.DeleteById(starDurationId)
	if err != nil {
		slog.Error("时间标签删除失败", "err", err)
		response.InternalServerErrorWithMsg(c, err.Error())
		return
	}
	response.SuccessWithData(c, httpModels.RespDeleteDurationTag{Id: starDurationId})
}
